const axios = require('axios');
const config = require('../config/credentials');
const { Octokit } = require('@octokit/rest');

/**
 * Service for interacting with Ocent API
 */
class OcentApiService {
    constructor(options = {}) {
        this.baseURL = options.baseURL || 'https://api.ocent.net/api/v1';
        this.credentials = options.credentials || {
            email: config.OCENT_EMAIL,
            password: config.OCENT_PASSWORD
        };
        this.token = null;
        this.userId = null;
        
        this.axiosInstance = axios.create({
            baseURL: this.baseURL,
            headers: {
                'Content-Type': 'application/json;charset=UTF-8',
                'Accept': 'application/json, text/plain, */*',
                'Accept-Language': 'zh-CN,zh;q=0.9',
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
                'Origin': 'https://ocent.net',
                'Referer': 'https://ocent.net/'
            }
        });
    }

    /**
     * Login to Ocent API and get authentication token
     * @returns {Promise<boolean>} Login success status
     */
    async login() {
        try {
            console.log('Logging in to ocent.net...');
            const response = await this.axiosInstance.post('/user/login', {
                email: this.credentials.email,
                password: this.credentials.password
            });

            if (response.data && response.data.status_code === 0) {
                this.token = response.data.data.token;
                this.userId = response.data.data.userId;
                
                this.axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${this.token}`;
                
                console.log('Login successful, user ID:', this.userId);
                return true;
            } else {
                console.error('Login failed:', response.data.status_msg);
                return false;
            }
        } catch (error) {
            console.error('Login request error:', error.message);
            if (error.response) {
                console.error('Error response:', error.response.data);
            }
            return false;
        }
    }

    /**
     * Get product information by ID
     * @param {number} productId - Product ID
     * @returns {Promise<Object|null>} Product data or null if failed
     */
    async getProductInfo(productId) {
        try {
            if (!this.token) {
                console.error('Not logged in, please call login method first');
                return null;
            }

            console.log(`Getting product details for ID:${productId}...`);
            const response = await this.axiosInstance.get(`/turbox/product/${productId}`, {
                headers: {
                    "authorization": this.token,
                    "cache-control": "no-cache",
                    "pragma": "no-cache",
                    "Referer": "https://ocent.net/",
                    "Referrer-Policy": "strict-origin-when-cross-origin"
                }
            });

            if (response.data && response.data.status_code === 0) {
                const productData = response.data.data;
                console.log('Product information retrieved successfully:', productData.name);
                return productData;
            } else {
                console.error('Failed to get product information:', response.data.status_msg);
                return null;
            }
        } catch (error) {
            console.error('Error getting product information:', error.message);
            if (error.response) {
                console.error('Error response:', error.response.data);
            }
            return null;
        }
    }
}

/**
 * Utility class for data processing
 */
class DataProcessor {
    /**
     * Format bytes to human-readable string
     * @param {number} bytes - Bytes
     * @returns {string} Formatted byte string
     */
    static formatBytes(bytes) {
        if (bytes === 0) return '0 B';
        const k = 1024;
        const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
        const i = Math.floor(Math.log(bytes) / Math.log(k));
        return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
    }

    /**
     * Parse IP addresses or domains from string
     * @param {string} ipString - String containing IP or domain
     * @returns {string[]} Array of parsed IPs or domains
     */
    static parseIpAddresses(ipString) {
        const ipPattern = /\d+\.\d+\.\d+\.\d+/g;
        const domainPattern = /[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9](?:\.[a-zA-Z]{2,})+/g;
        
        const ipMatches = ipString.match(ipPattern);
        if (ipMatches) {
            return ipMatches;
        }
        
        const domainMatches = ipString.match(domainPattern);
        if (domainMatches) {
            return [ipString];
        }
        
        return [];
    }

    /**
     * Generate proxy nodes from product data
     * @param {Object} productData - Product data from API
     * @returns {Array} Array of node configurations
     */
    static generateNodeConfigs(productData) {
        const nodeInfos = [];
        
        if (productData.connections && productData.connections.length > 0) {
            console.log(`This product has ${productData.connections.length} connections:`);
            
            productData.connections.forEach(conn => {
                productData.nodes.forEach(node => {
                    if (node.id === conn.node_id) {
                        const ips = this.parseIpAddresses(node.endpoints);
                        
                        ips.forEach((ip, index) => {
                            const nodeTemplate = {
                                name: `${conn.name}-${index}`,
                                type: "vmess",
                                server: ip,
                                port: conn.local_port,
                                uuid: config.PROXY_UUID,
                                alterId: 0,
                                cipher: "auto",
                                network: "tcp",
                                tls: false,
                                "skip-cert-verify": false
                            };
                            nodeInfos.push(nodeTemplate);
                        });
                    }
                });
            });
        }
        
        return nodeInfos;
    }

    /**
     * Convert node configs to YAML format
     * @param {Array} nodeInfos - Node configurations
     * @returns {string} YAML formatted string
     */
    static generateYamlConfig(nodeInfos) {
        let yamlOutput = 'proxies:\n';
        
        nodeInfos.forEach(node => {
            yamlOutput += `- {name: "${node.name}", server: ${node.server}, port: ${node.port}, type: ${node.type}, uuid: ${node.uuid}, alterId: ${node.alterId}, cipher: ${node.cipher}, tls: ${node.tls}, skip-cert-verify: ${node['skip-cert-verify']}}\n`;
        });
        
        return yamlOutput;
    }
}

/**
 * Service for GitHub Gist operations
 */
class GistService {
    constructor(options = {}) {
        this.gistId = options.gistId || config.GITHUB_GIST_ID;
        this.token = options.token || config.GITHUB_TOKEN;
        this.octokit = new Octokit({
            auth: this.token
        });
    }

    /**
     * Update a Gist with new content
     * @param {string} content - Content to update
     * @param {string} filename - Filename in the gist
     * @returns {Promise<Object>} Updated gist data
     */
    async updateGist(content, filename = 'proxies.yaml') {
        try {
            const gistResponse = await this.octokit.gists.update({
                gist_id: this.gistId,
                files: {
                    [filename]: {
                        content: content
                    }
                }
            });
            
            console.log('YAML uploaded to Gist:', gistResponse.data.html_url);
            return gistResponse.data;
        } catch (error) {
            console.error('Failed to upload to Gist:', error.message);
            throw error;
        }
    }
}

/**
 * Main service orchestrating the crawling process
 */
class OcentCrawlerService {
    constructor() {
        this.apiService = new OcentApiService();
        this.gistService = new GistService();
    }

    /**
     * Process product information and update Gist
     * @param {number} productId - Product ID to process
     * @returns {Promise<boolean>} Success status
     */
    async processProduct(productId) {
        try {
            // Login to API
            const loginSuccess = await this.apiService.login();
            if (!loginSuccess) {
                return false;
            }
            
            // Get product info
            const productData = await this.apiService.getProductInfo(productId);
            if (!productData) {
                return false;
            }
            
            // Generate node configs
            const nodeInfos = DataProcessor.generateNodeConfigs(productData);
            
            // Display usage statistics
            this._displayUsageStats(productData);
            
            // Generate YAML config
            const yamlConfig = DataProcessor.generateYamlConfig(nodeInfos);
            console.log(yamlConfig);
            console.log('Node information:', nodeInfos);
            
            // Try to upload to GitHub but don't affect the overall result
            try {
                await this.gistService.updateGist(yamlConfig);
                console.log('GitHub Gist update successful');
            } catch (gistError) {
                console.error('GitHub Gist update failed, but does not affect other functionality:', gistError.message);
                // Here we just log the error, won't interrupt the main process
            }
            
            return true;
        } catch (error) {
            console.error('Failed to process product information:', error.message);
            return false;
        }
    }
    
    /**
     * Display product usage statistics
     * @param {Object} productData - Product data
     */
    _displayUsageStats(productData) {
        const totalFlow = productData.flow;
        const usedFlow = productData.upload_used + productData.download_used;
        const usedPercent = ((usedFlow / totalFlow) * 100).toFixed(2);
        
        console.log(
            `Flow usage: ${usedPercent}%, ` +
            `Upload:${DataProcessor.formatBytes(productData.upload_used)}, ` +
            `Download:${DataProcessor.formatBytes(productData.download_used)}`
        );
    }
}

module.exports = new OcentCrawlerService();